package com.finance41.webtools.automerge;

import static java.nio.file.StandardWatchEventKinds.ENTRY_CREATE;
import static java.nio.file.StandardWatchEventKinds.ENTRY_DELETE;
import static java.nio.file.StandardWatchEventKinds.ENTRY_MODIFY;

import java.io.File;
import java.io.IOException;
import java.nio.file.FileSystems;
import java.nio.file.Path;
import java.nio.file.WatchEvent;
import java.nio.file.WatchKey;
import java.nio.file.WatchService;

/**
 * Executes an action when a file is modified.
 * If the given file is a directory, it will execute the action whenever a file inside the directory is created, deleted or modified.
 * Sometimes the action is called twice, but this it's not an issue for our use cases.
 * Does not work recursively for subfolders, but it will fire when a file is created or deleted in a direct subfolder.
 *
 * @author Ionut Bilica
 */
public class FileWatcher {

	public void watch(File target, final Action onFileModifiedAction) {
		final WatchService watcher;

		/*
		 * NIO watch has support only for directories, so, if the target is a file, we'll watch
		 * the parent directory and only execute the action when the target was modified.
		 */
		String tempFileName = null;
		try {
			watcher = FileSystems.getDefault().newWatchService();
			if (target.isDirectory()) {
				target.toPath().register(watcher, ENTRY_CREATE, ENTRY_DELETE, ENTRY_MODIFY);
			} else {
				tempFileName = target.getName();
				(new File(target.getAbsolutePath())).getParentFile().toPath().register(watcher, ENTRY_CREATE, ENTRY_DELETE, ENTRY_MODIFY);
			}
		} catch (IOException e) {
			throw new RuntimeException(e);
		}

		final String targetName = tempFileName;

		Thread t = new Thread() {
			@Override
			public void run() {
				watchFile(onFileModifiedAction, watcher, targetName);
			}
		};
		t.start();
	}

	private void watchFile(final Action onFileModifiedAction, final WatchService watcher, final String targetName) {
		while (true) {
			WatchKey key;
			try {
				key = watcher.take();
			} catch (InterruptedException x) {
				return;
			}

			for (WatchEvent<?> event : key.pollEvents()) {
				 WatchEvent<Path> ev = (WatchEvent<Path>)event;
			     Path filename = ev.context();
			     if (targetName == null || filename.toString().equals(targetName)) {
			    	 onFileModifiedAction.perform();
			     }
			}

			if (!key.reset()) {
				break;
			}
		}
	}
}
